/**
 * 
 */
package com.gr.experimental.model.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.gr.experimental.model.RVClass;
import com.gr.experimental.model.RVState;

/**
 * Utility class to help work with distributions. 
 * @author Karol Banczyk
 *
 * TODO: rename some methods to achieve some formal consistency
 */
public class DistributionUtil {

	public static class StateConfigurationDescriptor {
		int[][][] stateConfigurations;
		
		public StateConfigurationDescriptor(int[][][] stateConfigurations) {
			this.stateConfigurations = stateConfigurations;
		}
		
//		void printConfigurations() {
//			for (int i=0; i< confOrder.length; i++) {
//				int[] order = confOrder[i];
//				for (int j=0; j < order.length; j++) { // 
//					int[] config = stateConfigurations[j][order[j]];
//					System.out.print("[");
//					for (int k = 0; k < config.length; k++) {
//						System.out.print(config[k] + " ");
//					}
//					System.out.print("]");
//				}
//				System.out.println();
//			}
//		}
		
		public int indexOfConfig(int[][] configurations) {
			
			if (configurations.length != stateConfigurations.length) {
				throw new IllegalArgumentException("Wrong number of configurations");
			}
			
//			System.out.println("configuration[][]=" + deepArrayToString(configurations));

			int w=1;
			int index=0;
			for (int i = configurations.length - 1; i >= 0; i--) {
//				System.out.println("configuration[" + i +"]=" + intArrayToString(configurations[i]));
				int idx = indexOfStateConfig(stateConfigurations[i], configurations[i]);
//				System.out.println("idx[" +i+"]=" + idx +":: index of" + intArrayToString(configurations[i]) + " in " + deepArrayToString(configurations));
				if (idx == -1) {
					//throw new IllegalArgumentException("for i: " + i + " config not found");
					return -1;
				}
				index += idx *w;
				w *= stateConfigurations[i].length;
			}

			return index;
		}
		
		public int[][][] getComplexStateConfigurations() {
			int size = countStateVariationSize();
			int[][][] result = new int[size][stateConfigurations.length][];

			for (int i=0; i < size; i++) {
				int w = 1;
				for (int j=0; j < stateConfigurations.length; j++) { // 
					int r = w / stateConfigurations[j].length; 
					w %= stateConfigurations[j].length;
					result[i][j] = stateConfigurations[j][r];
				}
			}
			
			return result;
		}

		
		public void writeStateVariation(int w, int[] out){
			
			for (int k=0; k < stateConfigurations.length; k++) {
				out[k] = w / stateConfigurations[k].length; 
				w %= stateConfigurations[k].length;
			}
		}

		public int countStateVariationSize() {
			int w=1;
			for (int i=0; i < stateConfigurations.length; i++) {
				w *= stateConfigurations[i].length;
			}
			return w;
		}
		
	};
	
	public List<List<RVStateImpl>> getPossibleParentStates(List<RVClassImpl> rvClasses) {

		List<List<RVStateImpl>> result = new ArrayList<List<RVStateImpl>>();
		if (rvClasses.size() > 0) {
			
			RVClass rvClass = rvClasses.get(0);
			
			for (RVState state: rvClass.getDomain()) {
				List<List<RVStateImpl>> otherStates = getPossibleParentStates(rvClasses.subList(1, rvClasses.size()));
				
				for (List<RVStateImpl> rvStateList: otherStates) {
					List<RVState> row = new ArrayList<RVState>();
					row.add(state);
					row.addAll(rvStateList);
				}
			}
		}
		
		return result;
	}
	
	/**
	 * Computes the number of distinct state configuration.
	 * If there are n state holders each able of taking one of k possible states then
	 * we call an state assignment of every variable o state configuration. Two
	 * state configuration are different if there is at least one state is taken by
	 * a different number of state holders otherwise they are the same.
	 * @param stateCount
	 * @param slots
	 * @return
	 */
	public static long computeStateConfigurationsNumber(long stateHolders, long stateNumber) {
		
		long n = stateHolders;
		long k = stateNumber;
		
		// equal to Newt(n + k - 1, k - 1)
		return numComb(n + k - 1, k - 1);
	}
	
	public static StateConfigurationDescriptor createStateConfigurationDescriptor(int[][] stateHolderNumberPairs) {
		
		int[][][] stateConfigurations = new int[stateHolderNumberPairs.length][][];
		
		int[] stateNumbers = new int[stateHolderNumberPairs.length];
		for (int i=0; i < stateHolderNumberPairs.length; i++) {
			int stateHolders = stateHolderNumberPairs[i][0];
			int stateNumber= stateHolderNumberPairs[i][1];
			stateConfigurations[i] = generateStateConfigurations(stateHolders, stateNumber);
			stateNumbers[i] = stateConfigurations[i].length;
		}
		
//		System.out.println("stateNumbers: " + intArrayToString(stateNumbers));
		return new StateConfigurationDescriptor(stateConfigurations);
	}

	public static int[][][] generateComplexStateConfigurations(int[][] stateHolderNumberPairs) {
		
		int[][][] stateConfigurations = new int[stateHolderNumberPairs.length][][];
		
		int[] stateNumbers = new int[stateHolderNumberPairs.length];
		for (int i=0; i < stateHolderNumberPairs.length; i++) {
			int stateHolders = stateHolderNumberPairs[i][0];
			int stateNumber= stateHolderNumberPairs[i][1];
			stateConfigurations[i] = generateStateConfigurations(stateHolders, stateNumber);
			stateNumbers[i] = stateConfigurations[i].length;
		}
		
		int[][] confOrder = generateConfigurationIndexes(stateNumbers);
		
		int[][][] result = new int[confOrder.length][confOrder[0].length][];

		for (int i=0; i< confOrder.length; i++) {
			int[] order = confOrder[i];
			for (int j=0; j < order.length; j++) { // 
				result[i][j] = stateConfigurations[j][order[j]];
			}
		}
		
		return result;
	}

	public static int getIndexOfConfigurationIndex(int[] bases, int[] index) {
	
		int w=1;
		int r=0;
		for (int i=bases.length; i>=0 ; i++) {
			r += index[i] *w;
			w*=bases[i];
		}
		return r;
	}
	
	
	public static int[][] generateConfigurationIndexes(int[] stateNumbers) {
		
		int rows = 1;
		int stateHolders = stateNumbers.length;
		int[] coefs = new int[stateHolders];
		for (int i=stateHolders - 1; i >= 0; i--) {
			if (stateNumbers[i] <= 0) throw new IllegalArgumentException("stateNumbers[" + i +"] <= 0");
			coefs[i] = rows;
			rows *=  stateNumbers[i];
		}
		
		int[][] vars = new int[rows][stateNumbers.length];
		
		for (int i=0; i< rows; i++) {
			int w = i;
			for (int k=0; k < stateHolders; k++) {
				vars[i][k] = w / coefs[k]; 
				w %= coefs[k];
			}
		}
		
		return vars;
	}


	
	/**
	 * Counts the index of a state config in conventionally orders configs.
	 * @param configs configs in conventional ordering
	 * @param config the config
	 * @return 
	 */
	public static int indexOfStateConfig(int[][] configs, int[] config) {
		
		for (int i=0; i< configs.length; i++) {
			if (Arrays.equals(configs[i], config)) {
				return i;
			}
		}
		return -1;
	}
	
	
	public static int[][] generateStateConfigurations(int stateHolders, int stateNumber) {
		int confCount = (int) computeStateConfigurationsNumber(stateHolders, stateNumber);
		
		int[][] configs = new int[confCount][stateNumber];
		int[] prefix = new int[stateNumber];
		generateStateConfigurations(stateHolders, configs, 0, 0, prefix);
		
		return configs;
	}
	
	public static String intArrayToString(int[] array) {
		StringBuilder builder = new StringBuilder();
		builder.append("[");
		for (int i=0; i < array.length; i++) {
			builder.append(array[i]).append(" ");
		}
		builder.append("]");
		return builder.toString();
	}

	public static String doubleArrayToString(double[] array) {
		StringBuilder builder = new StringBuilder();
		builder.append("[");
		for (int i=0; i < array.length; i++) {
			builder.append(array[i]).append(" ");
		}
		builder.append("]");
		return builder.toString();
	}
	
	public static String deepArrayToString(Object array) {
		
		if (array instanceof int[]) {
			return intArrayToString((int[])array);
		} else if (array instanceof double[]) {
			return doubleArrayToString((double[]) array);
		}  
		// deep array
		Object[] arr = (Object[]) array;
		StringBuilder builder = new StringBuilder();
		builder.append("[");
		for (int i=0; i< arr.length; i++) {
			builder.append(deepArrayToString(arr[i])).append(" ");
		}
		builder.append("]");
		return builder.toString();
	}


	private static int generateStateConfigurations(int stateHolders, int[][] configs, int row, int col, int[] prefix) {
		
		int r = row;
		if (col == configs[r].length - 1) { // end of a row
			for (int i=0; i < col; i++) { // copy the prefix to current row
				configs[r][i] = prefix[i];
			}
			configs[r][col] = stateHolders;
			return 1;
		}
		
		for (int state= stateHolders; state >=0; state --) {
			prefix[col] = state;
			r += generateStateConfigurations(stateHolders - state, configs, r, col + 1, prefix);
			 
		}
		
		return r - row;
	}

	
	
	
	public static long numComb(long n, long r)
	   {
	      // n!/(r!(n-r)!)
	      // nCr = nc(r-1) + (n-1)Cr
	      if (r == n)
	         return 1;
	      else if (r < 0)
	         throw new IllegalArgumentException ("r = "+r+" must be at least 0");
	      else if (r > n)
	         throw new IllegalArgumentException ("r = "+r+" must be at most n = " + n);
	      else
	         return (r+1) * numComb (n,r+1) / (n-r);
	   }
	
}
